﻿using MongoDB.Bson;
using Sustainalytics.Common.Services;
using Sustainalytics.Entities;
using Sustainalytics.OAuth.Services.Common;
using Sustainalytics.Portfolios.BL.Model;
using Sustainalytics.ScreeningTool.BL;
using Sustainalytics.ScreeningTool.Service.Map;
using Sustainalytics.ScreeningTool.Service.Utility;
using Sustainalytics.Utils;
using Sustainalytics.Utils.Serilog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Cors;

namespace Sustainalytics.ScreeningTool.Service
{
    [EnableCors(origins: "*", headers: "*", methods: "*")]
    [RoutePrefix("api/v1/runs/{runId}/results")]
    public class RunResultsController : ApiController, IRunResultsController
    {
        private readonly ICompanyScreening _companyScreening;

        public RunResultsController(ICompanyScreening companyScreening)
        {
            try
            {
                CommonEventSerilog.Log.InitController();

                _companyScreening = companyScreening;

                CommonEventSerilog.Log.InitControllerSuccess();
            }
            catch (Exception ex)
            {
                CommonEventSerilog.Log.InitControllerError(ex);
                throw new HttpResponseException(HttpStatusCode.InternalServerError);
            }
        }
        [Route("")]
        public HttpResponseMessage Get([FromUri] Guid runId, [FromUri] RunResultsOptionsDto runResultsOptionsDto)
        {
            var filterId = runId;
            var options = runResultsOptionsDto ?? RunResultsOptionsDto.Default;
            var portfolioSecuritiesCount = 0;
            var toRemoveCount = 0;

            //get SecurityName, CompanyId for the provided portfolioId
            var portfolioSecurities = new List<PortfolioSecurityModel>();

            var filter = new ResultOutputFilter
            {
                FilterId = filterId,
                IsValid = options.IsValid
            };

            var resultsOutput = _companyScreening.GetResultOutput(filter, options.Skip, options.Take,
                options.OrderBy, options.OrderAscending);

            var resultOutputCompanyIds = resultsOutput.Select(r => r["CompanyId"].AsGuid).ToList();

            //get also additional company ids from client portfolio

            if (options.PortfolioId != null && resultOutputCompanyIds.Any() && resultOutputCompanyIds.All(d => d != Guid.Empty))
            {
                var userClaims = new PortfolioUserClaimsProcessor().GetPortfolioClaims();
                portfolioSecurities = _companyScreening.GetClientPortfolioSecurities(options.PortfolioId.Value, userClaims);
                resultOutputCompanyIds.AddRange(portfolioSecurities.Select(x => x.CompanyId));
                resultOutputCompanyIds = resultOutputCompanyIds.Distinct().ToList();
            }

            //gets ScreeningData for the company Ids
            var companydata = _companyScreening.GetScreeningDataFields(filterId,
                (resultOutputCompanyIds.All(a => a == Guid.Empty)) ? new List<Guid>() : resultOutputCompanyIds);

            var resultsDtos = new List<RunResultDto>();
            if (!resultOutputCompanyIds.Contains(Guid.Empty) && resultsOutput != null && companydata.Any())
            {
                var map = ServiceProvider.Single<IMap<RunResultDto, BsonDocument>>();

                foreach (var resultOutput in resultsOutput.Distinct(new CompareById()))
                {
                    foreach (var companyData in companydata[resultOutput["CompanyId"].AsGuid])
                    {
                        if (!resultOutput.Contains(companyData.Name))
                        {
                            resultOutput.Add(companyData.Name, companyData.Value);
                        }
                        else
                        {
                            resultOutput[companyData.Name] = companyData.Value;
                        }
                    }

                    var dto = map.Map(resultOutput);

                    dto.Criteria = dto.Criteria.Where(a => !a.IsTemporary).ToList();

                    //Create a new resultOutput for every company in ClientPortfolio collection, without the original company from ResultOutput
                    if (portfolioSecurities == null)
                    {
                        resultsDtos.Add(dto);
                        continue;
                    }

                    var matchedSecurities = portfolioSecurities.Where(x => x.CompanyId == resultOutput["CompanyId"].AsGuid).ToList();

                    if (!matchedSecurities.Any())
                    {
                        resultsDtos.Add(dto);
                        continue;
                    }

                    matchedSecurities.ForEach((security) =>
                    {
                        var newResultOutput = new BsonDocument(resultOutput)
                        {
                            {"SecurityId", security.SecurityId.ToString() },
                            {"SecurityIdentifier", security.DisplayInfo.SecurityIdentifier },
                            {"CustomSecurityIdentifierName", security.DisplayInfo.CustomSecurityIdentifierName ?? string.Empty }
                        };

                        portfolioSecuritiesCount++;
                        resultsDtos.Add(map.Map(newResultOutput));
                    });

                    toRemoveCount++;
                }
            }

            var result = new GetRunResultDto { Items = resultsDtos };

            if (options.GetCount)
            {
                result.Count = _companyScreening.CountResultOutput(filter) + portfolioSecuritiesCount - toRemoveCount;
            }

            result.Options = options;

            //if the resultDtos is empty than we search the criteria

            if (result.Items == null || (result.Items != null && !result.Items.Any()))
            {
                var mapperCriteria = new CriteriaResultMap();

                var dataCriteria = _companyScreening.GetCriteriaResultOutput(filterId);
                var listCriteria = dataCriteria.Select(criteriaResultOutput => mapperCriteria.Map(criteriaResultOutput)).ToList();
                result.Items = new List<RunResultDto> { new RunResultDto { Criteria = listCriteria } };
            }
            
            return Request.CreateResponse(HttpStatusCode.OK, result);
        }
    }

    internal class CompareById : IEqualityComparer<BsonDocument>
    {
        const int _multiplier = 89;

        public bool Equals(BsonDocument x, BsonDocument y)
        {
            return x["CompanyId"].AsGuid == y["CompanyId"].AsGuid;
        }

        public int GetHashCode(BsonDocument obj)
        {
            int result = 0;
            if (obj == null)
            {
                return 0;
            }

            // Get length.
            int length = obj.ToString().Length;

            // Return default code for zero-length strings [valid, nothing to hash with].
            if (length > 0)
            {
                // Compute hash for strings with length greater than 1
                char let1 = obj.ToString()[0];          // First char of string we use
                char let2 = obj.ToString()[length - 1]; // Final char

                // Compute hash code from two characters
                int part1 = let1 + length;
                result = (_multiplier * part1) + let2 + length;
            }
            return result;
        }
    }
    }
